﻿using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace PC.PickleJar.VisualStudioIntegration.AutoCompletion
{
	class SourceItemCompletionCommandHandler : IOleCommandTarget
	{
		private IOleCommandTarget nextCommandHandler;
		private ITextView textView;
		private SourceItemCompletionHandlerProvider sourceItemCompletionHandlerProvider;
		private ICompletionSession session;

		public SourceItemCompletionCommandHandler(IVsTextView textViewAdapter, ITextView textView, SourceItemCompletionHandlerProvider sourceItemCompletionHandlerProvider)
		{
			this.textView = textView;
			this.sourceItemCompletionHandlerProvider = sourceItemCompletionHandlerProvider;

			textViewAdapter.AddCommandFilter(this, out this.nextCommandHandler);
		}

		public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
		{
			if (VsShellUtilities.IsInAutomationFunction(this.sourceItemCompletionHandlerProvider.ServiceProvider))
			{
				return this.nextCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
			}

			uint commandID = nCmdID;
			char typedCharacter = char.MinValue;

			if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.TYPECHAR)
			{
				typedCharacter = (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn);
			}

			// check for a commit character 
			if (nCmdID == (uint)VSConstants.VSStd2KCmdID.RETURN
				|| nCmdID == (uint)VSConstants.VSStd2KCmdID.TAB
				|| (char.IsWhiteSpace(typedCharacter) || char.IsPunctuation(typedCharacter)))
			{
				if (this.session != null && !this.session.IsDismissed)
				{
					// if the selection is fully selected, commit the current session 
					if (this.session.SelectedCompletionSet.SelectionStatus.IsSelected)
					{
						this.session.Commit();
						// also, don't add the character to the buffer 
						return VSConstants.S_OK;
					}
					else
					{
						// if there is no selection, dismiss the session
						this.session.Dismiss();
					}
				}
			}

			// pass along the command so the char is added to the buffer 
			int result = this.nextCommandHandler.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
			bool isHandled = false;

			if (!typedCharacter.Equals(char.MinValue) && char.IsLetterOrDigit(typedCharacter))
			{
				if (this.session == null || this.session.IsDismissed) // If there is no active session, bring up completion
				{
					this.TriggerCompletion();
					this.session.Filter();
				}
				else     // the completion session is already active, so just filter
				{
					this.session.Filter();
				}
				isHandled = true;
			}
			else if (commandID == (uint)VSConstants.VSStd2KCmdID.BACKSPACE   // redo the filter if there is a deletion
				|| commandID == (uint)VSConstants.VSStd2KCmdID.DELETE)
			{
				if (this.session != null && !this.session.IsDismissed)
					this.session.Filter();
				isHandled = true;
			}

			if (isHandled)
			{
				result = VSConstants.S_OK;
			}

			return result;
		}

		private void TriggerCompletion()
		{
			// the caret must be in a non-projection location 
			SnapshotPoint? caretPoint =
				this.textView.Caret.Position.Point.GetPoint(
				textBuffer => (!textBuffer.ContentType.IsOfType("projection")), PositionAffinity.Predecessor);

			if (caretPoint.HasValue)
			{
				this.session = this.sourceItemCompletionHandlerProvider.CompletionBroker.CreateCompletionSession(
					this.textView,
					caretPoint.Value.Snapshot.CreateTrackingPoint(caretPoint.Value.Position, PointTrackingMode.Positive),
					true);

				// subscribe to the Dismissed event on the session 
				this.session.Dismissed += this.OnSessionDismissed;
				this.session.Start();
			}
		}

		private void OnSessionDismissed(object sender, EventArgs e)
		{
			this.session.Dismissed -= this.OnSessionDismissed;
			this.session = null;
		}

		public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
		{
			return this.nextCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
		}
	}
}
